package common;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import moTSP.TspGenotype;
import elEMAS.inf.Agent;

import salb1.Salb1Genotype;


public class GeneticHelper {
    
    /**
     * Oblicza Hiper Valume
     * @param <T>
     * @param population
     * @param ref_f0 współrzędna f1 punktu referencyjnego, musi być większa wartości f1 wszystkich osobników
     * @param ref_f1 współrzędna f2 punktu referencyjnego, musi być większa wartości f2 wszystkich osobników
     * @return
     */
    public static <T extends BasicAgent<?>> double calculateHiperValume(List<T> population, double ref_f0, double ref_f1) {
        
        if (population.size() == 0) {
            return 0.0;
        }
        
        Collections.sort(population, new Comparator<BasicAgent<?>>() {
            public int compare(BasicAgent<?> o1, BasicAgent<?> o2) {
                return Double.compare(o1.getFitness(0),o2.getFitness(0));
            }     
        });
        BasicAgent<?> last = population.get(population.size()-1);
        double max_f0 = last.getFitness(0);
        
        BasicAgent<?> max_f1_agent = Collections.max(population, new Comparator<BasicAgent<?>>() {
            public int compare(BasicAgent<?> o1, BasicAgent<?> o2) {
                return Double.compare(o1.getFitness(1),o2.getFitness(1));
            }     
        });
        double max_f1 = max_f1_agent.getFitness(1);
        
        if (!(ref_f0 > max_f0)) {
            throw new IllegalArgumentException("ref_f0 = "+ref_f0+", max_f0 = "+max_f0);
        }
        if (!(ref_f1 > max_f1)) {
            throw new IllegalArgumentException("ref_f1 = "+ref_f1+", max_f1 = "+max_f1+", f0 = "+max_f1_agent.getFitness(0));
        }
        
        double sum = (ref_f1 - last.getFitness(1)) * (ref_f0 - last.getFitness(0));
        
        //System.out.println("ref_f0 "+ref_f0+" max_0 "+last.getFitness(0));
        //System.out.println("ref_f1 "+ref_f1+" max_1 "+max_f1);
        
        for (int j = population.size()-1; j > 0; j--) {
            
            BasicAgent<?> agent = population.get(j);
            BasicAgent<?> prev_agent = population.get(j-1);
            
            sum += (ref_f1 - prev_agent.getFitness(1)) * (agent.getFitness(0)-prev_agent.getFitness(0));
        }
        
        return sum;
    }

    /**
     * Oprócz wartości zapisuje do pliku także genotyp. Genotyp musi przeciążyć operacje toString().
     * @param <T>
     * @param population
     * @return
     */
    public static <T extends BasicAgent<?>> String populationToString(List<T> population) {
        
        StringBuffer sb = new StringBuffer();
        for (T ind : population) {
            
            List<Double> values = ind.getValues();
            String valuesStr = CommonHelper.join(values, ", ");
            sb.append(valuesStr+" ");
            sb.append(ind.getGenotype());
            sb.append("\n");
        }
        return sb.toString();
    }

    /**
     * Zapisuje do stringa, który po zapisaniu do pliku da się wyświetlić w gnuplot
     * @param <T>
     * @param population
     * @return
     */
    public static <T extends BasicAgent<?>> String populationValuesToString(List<T> population) {
        
        StringBuffer sb = new StringBuffer();
        for(T ind : population) {
            String valuesStr = CommonHelper.join(ind.getFitnesses(), ", ");
            sb.append(valuesStr).append("\n");
        }
        return sb.toString();
    }
    
    public static <T extends BasicAgent<?>> void populationValuesToFile(List<T> population, String filename) {
        
        String str = populationValuesToString(population);
        CommonHelper.printToFile(filename, str);
    }
    
    
    
    /**
     * Założenie jest że minimalizujemy fitness.<br>
     * Zwraca:<br>
     * 1  pierwszy agent dominuje <br>
     * 0  remis,<br>
     * -1 drugi agent dominuje
     */
    public static <T extends BasicAgent<?>> int checkDomination(T agent1, T agent2) {
        
        if (agent1.getFitnesses().size() != 2) {
            throw new RuntimeException("Implemented only for two objectives");
        }
	    
	    double first_f1 = agent1.getFitness(0);
	    double first_f2 = agent1.getFitness(1);
	    double second_f1 = agent2.getFitness(0);
	    double second_f2 = agent2.getFitness(1);
	    
		int result = 0;
	    
	    if(  ( (first_f1 <= second_f1) && (first_f2 <  second_f2) )
	      || ( (first_f1 <  second_f1) && (first_f2 <= second_f2) )) 
	    {
			result = 1;
	    }
	    if(  ( (second_f1 <= first_f1) && (second_f2 <  first_f2) )
	      || ( (second_f1 <  first_f1) && (second_f2 <= first_f2) )) 
	    {
	        result = -1;
	    }
	    return result;
	}
        
    /**
     * Łączy dwie listy z osobnikami w jedną zawierającą niezdominowane osobniki z obu.<br>
     * ZAŁOŻENIE: W obrębie każdej listy osobniki powinny być niezdominowane.
     */
    public static <T extends BasicAgent<?>> List<T> merge(List<T> population1, List<T> population2) {
        
        List<T> population1NonDominated = new LinkedList<T>(population1);
        List<T> population2NonDominated = new LinkedList<T>(population2);
            
        Iterator<T> population1NonDominatedIt = population1NonDominated.iterator();
        Iterator<T> population2NonDominatedIt = population2NonDominated.iterator();
            
        while (population1NonDominatedIt.hasNext()) {     
            T population1Agent = population1NonDominatedIt.next();

            for (T population2Agent : population2NonDominated) {
                
                if (population1Agent.getGenotype().equals(population2Agent.getGenotype())) {
                    population1NonDominatedIt.remove();
                }
                else
                if (checkDomination(population1Agent, population2Agent) == -1) { // drugi jest lepszy
                    population1NonDominatedIt.remove();
                    break;
                }
            }           
        }
        
        while (population2NonDominatedIt.hasNext()) {     
            T population2Agent = population2NonDominatedIt.next();

            for (T population1Agent : population1NonDominated) {
                if (checkDomination(population2Agent, population1Agent) == -1) { // drugi jest lepszy
                    population2NonDominatedIt.remove();
                    break;
                }
            }           
        }
        
        population1NonDominated.addAll(population2NonDominated);
        return population1NonDominated;
    }

    /**
     * Zwraca listę w której nie ma osobników zdominowanych.
     * @param <T>
     * @param population
     * @return
     */
    public static <T extends BasicAgent<?>> List<T> withoutDominated(List<T> population) {
        
        List<T> result = new LinkedList<T>(population);
        Iterator<T> iter = result.iterator();
        
        while (iter.hasNext()) {
            T ind = iter.next();
            
            for (T populationInd : population) {
                if (ind != populationInd && populationInd.checkDomination(ind) == 1) {
                    iter.remove();
                    break;
                }
            }
        }
        return result;

    }
    
    public static <T> List<BasicAgent<T>> readPopulationFromFile(GeneticOperations<T> go, String fileName) throws IOException {
        
        BufferedReader individualsReader = new BufferedReader(new FileReader(fileName)); 
        
        List<BasicAgent<T>> agents = new LinkedList<BasicAgent<T>>();
        
        String str;
        while ((str = individualsReader.readLine()) != null) {
            
            String genotypeStr = str.split(" ")[2];
            T genotype = go.createGenotypeFromString(genotypeStr);
            agents.add(new BasicAgentImpl<T>(go,genotype));
        }
        return agents;
    }
    
    /**
     * Sprawdza czy te dwa osobniki nie mają jednakowego genotypu.
     * Najpierw sprawdza wartości czy są bliskie (szybkie), a potem equals na genotypach (kosztowne). żeby działało szybciej
     */
    public static <Genotype> boolean isDuplicate (BasicAgent<Genotype> agent1, BasicAgent<Genotype> agent2, double epsilon) {
        
        int numOfObjectives = agent1.getValues().size();
        for (int i = 0; i < numOfObjectives; i++) {
            if ( Math.abs(agent1.getValue(i) - agent2.getValue(i)) > epsilon ) {
                return false;
            }
        }
        return agent1.getGenotype().equals(agent2.getGenotype());
    }

    /**
     * Czyta osobniki z pliku <i>bestGenotypeFile</i> łączy je z osobnikami z <i>population</i>,
     * tak aby zostały tylko niezdominowane. Niezdominowane są zapisywane z powrotem do <i>bestGenotypeFile</i>.
     * @param bestFile plik z nalepszymi osobnikami - genotypy
     * @param bestGenotypeFile plik z najlepszymi osobniki wartości
     * @param population nowo dodawane osobniki
     */
    @SuppressWarnings("unchecked")
    public static <Genotype> void updateBest(GeneticOperations<Genotype> go, String bestFile, String bestGenotypeFile,
            List population, double ref_f0, double ref_f1) {
        
        List<BasicAgent<Genotype>> bestAgents;
        try {
            bestAgents = GeneticHelper.readPopulationFromFile(go, bestGenotypeFile);
            System.out.println("HVR dotychczas najlepszych "+GeneticHelper.calculateHiperValume(bestAgents, ref_f0, ref_f1));
        }
        catch (IOException e) {
            System.out.println("UWAGA Nie było listy agentów");
            bestAgents = new LinkedList<BasicAgent<Genotype>>();
        }
        List<BasicAgent<Salb1Genotype>> newBestAgents = GeneticHelper.merge(bestAgents, (List)population);

        int numOfCommon = CommonHelper.countCommonObjects(bestAgents,newBestAgents);
        int added = newBestAgents.size() - numOfCommon;
        System.out.println("Do listy najlepszych zostało dodanych "+added+" agentów");
        System.out.println("HVR najlepszych teraz "+GeneticHelper.calculateHiperValume(newBestAgents, ref_f0, ref_f1));
        

        String newBestGenStr = GeneticHelper.populationToString(newBestAgents);
        String newBestStr = GeneticHelper.populationValuesToString(newBestAgents);
        CommonHelper.printToFile(bestGenotypeFile, newBestGenStr);
        CommonHelper.printToFile(bestFile, newBestStr);
        
    }

    public static <Genotype> void savePopulationToFile(String prefix,
            List<? extends BasicAgent<Genotype>> population) {
        
        String agentsStr = GeneticHelper.populationValuesToString(population);
        String agentsGenotypesStr = GeneticHelper.populationToString(population);
        
        CommonHelper.printToFile(prefix+".txt",agentsStr);
        CommonHelper.printToFile(prefix+"_gen.txt", agentsGenotypesStr);
    }
    
}
